package com.panopset;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import javax.swing.text.JTextComponent;

/**
 * General static utilities.
 *
 * @author Karl Dinwiddie
 */
public final class Util {

    /**
     * 1000 milliseconds.
     */
    public static final long ONE_SECOND = 1000;

    /**
     * ONE_SECOND * 60.
     */
    public static final long ONE_MINUTE = ONE_SECOND * 60;

    /**
     * ONE_MINUTE * 60.
     */
    public static final long ONE_HOUR = ONE_MINUTE * 60;

    /**
     * ONE_HOUR * 24.
     */
    public static final long ONE_DAY = ONE_HOUR * 24;

    /**
     * ONE_DAY * 7.
     */
    public static final long ONE_WEEK = ONE_DAY * 7;

    /**
     * Timestamp up to seconds <b>yyyy-MM-dd HH:mm:ss</b>.
     */
    public static final SimpleDateFormat TIMESTAMP_SECONDS
            = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * <b>MMMMMMMMMMM dd, yyyy</b>.
     */
    public static final SimpleDateFormat TIMESTAMP_FORMAT
            = new SimpleDateFormat("MMMMMMMMMMM d, yyyy");

    /**
     * <b>HH:mm:ss MMMMMMMMMMM dd, yyyy</b>.
     */
    public static final SimpleDateFormat CLOCK_FORMAT = new SimpleDateFormat(
            "KK:mm:ss aa MMMMMMMMMMM dd, yyyy ");

    /**
     * <b>HH:mm:ss MMMMMMMMMMM dd, yyyy</b>.
     */
    public static final SimpleDateFormat VERSION_FORMAT = new SimpleDateFormat(
            "yyyyMMdd");

    /**
     * <b>EEEE, MMMM dd, yyyy, h:mm a(zz)</b>.
     */
    public static final SimpleDateFormat LAST_MODIFIED_FORMAT
            = new SimpleDateFormat("EEEE, MMMM dd, yyyy, h:mm a(zz)");

    /**
     * Clear all text components, including those in parent containers.
     *
     * @param c
     *            Container to have all editable text fields cleared for.
     */
    public static void clearAllTextComponents(final Container c) {
        if (c == null) {
            return;
        }
        Container p = c.getParent();
        while (p != null && p != c) {
            clearAllTextComponents(p);
            return;
        }
        clearAllSubText(c);
    }

    /**
     * Clear all text components in a container.
     *
     * @param c
     *            Container for which all text components are to be cleared.
     */
    public static void clearAllSubText(final Container c) {
        for (Component s : c.getComponents()) {
            if (s instanceof JTextComponent) {
                JTextComponent t = (JTextComponent) s;
                if (t.isEditable()) {
                    t.setText("");
                }
            } else if (s instanceof Container) {
                clearAllSubText((Container) s);
            }
        }
    }

    /**
     * @return true if verbose messages flag is true, default is false.
     */
    public static boolean isVerbose() {
        return verbose;
    }

    /**
     * Sets logging to verbose.
     */
    public static void setVerbose() {
        verbose = true;
    }

    /**
     * Used by vlog, iff true, vlog will log a message.
     */
    private static boolean verbose = false;

    /**
     * Log only if verbose set to true.
     *
     * @param msg
     *            Message.
     */
    public static void vlog(final String msg) {
        if (verbose) {
            log(msg);
        }
    }

    /**
     * Convert a Vector<String> to a String array.
     *
     * @param vector
     *            Vector.
     * @return array Array.
     */
    public static String[] toArray(final Vector<String> vector) {
        String[] rtn = new String[vector.size()];
        int i = 0;
        for (String s : vector) {
            rtn[i++] = s;
        }
        return rtn;
    }

    /**
     * Copy one String map to another.
     *
     * @param fromMap
     *            Map to copy from.
     * @param toMap
     *            Map to copy to. Existing entries are kept or written over.
     * @return toMap if toMap is null, a new map will be created.
     */
    public static Map<String, String> copyMap(
            final Map<String, String> fromMap,
            final Map<String, String> toMap) {
        Map<String, String> rtn = toMap;
        if (rtn == null) {
            rtn = new HashMap<String, String>();
        }
        if (fromMap != null) {
            for (Entry<String, String> entry : fromMap.entrySet()) {
                rtn.put(entry.getKey(), entry.getValue());
            }
        }
        return rtn;
    }

    /**
     * Verbose log.
     *
     * @param msg
     *            Message to log if verbose is true.
     */
    public static void logv(final String msg) {
        if (verbose) {
            log(msg);
        }
    }

    /**
     * Log writer.
     */
    private static final StringWriter LOG_WRITER = new StringWriter();

    /**
     * @return Log.
     */
    public static String getLog() {
        return LOG_WRITER.toString();
    }

    /**
     * Log a String.
     *
     * @param msg
     *            Message to log.
     */
    public static void log(final String msg) {
        LOG_WRITER.append(msg).append(Strings.getEol());
        System.out.println(msg);
    }

    /**
     * Log a Throwable stack trace.
     *
     * @param t
     *            Throwable
     */
    public static void log(final Throwable t) {
        Alert.red(t.getMessage());
        log(getFullStackTrace(t));
    }

    /**
     * Display a message.
     *
     * @param msg
     *            Message to display.
     */
    public static void dspmsg(final String msg) {
        Alert.clearAll();
        Alert.green(msg);
    }

    /**
     * Clear messages.
     */
    public static void clrmsg() {
        Alert.clearAll();
    }

    /**
     * Stub class for future Nation Language System use, doesn't do anything.
     *
     * @param s
     *            String to translate.
     * @return s Translated string.
     */
    public static String x(final String s) {
        return s;
    }

    /**
     * Generic dump. Collections and dumps are nicely formatted.
     *
     * @param o
     *            Object to be given a clean dump for.
     * @return String representation of given Object.
     */
    @SuppressWarnings("unchecked")
    public static String dump(final Object o) {
        StringWriter sw = new StringWriter();
        if (o == null) {
            return x("Can not dump null object") + ".";
        }
        if (o instanceof Object[]) {
            for (Object s : (Object[]) o) {
                sw.append(dump(s));
            }
        } else if (o instanceof Map) {
            Map<Object, Object> m = (Map<Object, Object>) o;
            for (Entry<Object, Object> entry : m.entrySet()) {
                sw.append(entry.getKey().toString());
                sw.append(Strings.getEol());
                Object v = entry.getValue();
                if (v == null) {
                    sw.append("null");
                } else {
                    sw.append(v.toString());
                }
                sw.append(Strings.getEol());
                sw.append(Strings.getEol());
            }
        } else if (o instanceof Collection) {
            for (Object e : (Collection) o) {
                sw.append(dump(e));
            }
        } else {
            sw.append(o.toString());
            sw.append(Strings.getEol());
        }
        return sw.toString();
    }

    /**
     * Invoke a static method.
     * <ul>
     * <li>It must return a String</li>
     * </ul>
     *
     * &#064;param classMethodAndParms Example:
     *
     * <pre>
     * com.panopset.Util.capitalize(foo)
     *
     * </pre>
     *
     * @param classMethodAndParms
     *            Class method and params.
     * @return String result of method invocation.
     * @throws Exception
     *             Exception.
     */
    public static String invokeStaticStringMethod(
            final String classMethodAndParms) throws Exception {
        return invokeStaticStringMethod(classMethodAndParms, null);
    }

    /**
     * Invoke a static method.
     * <ul>
     * <li>It must return a String</li>
     * <li>The parameters are keys to a map.</li>
     * </ul>
     *
     * <pre>
     * &#064;param classMethodAndParms
     * Example:
     * com.panopset.Util.capitalize(foo)
     * Note that if foo is not in the map, the value passed will be foo
     * itself.
     * </pre>
     *
     * @param classMethodAndParms
     *            Class method and params.
     * @param mapProvider
     *            Can be null if there are no parameters.
     * @return String result of invoking method.
     * @throws Exception
     *             Exception.
     */
    public static String invokeStaticStringMethod(
            final String classMethodAndParms, final MapProvider mapProvider)
            throws Exception {
        return new ReflectionInvoker.Builder().classMethodAndParms(
                classMethodAndParms).mapProvider(mapProvider).construct()
                .exec();
    }

    /**
     * Capitalize first letter in a String.
     *
     * @param s
     *            String to capitalize
     * @return Capitalized value.
     */
    public static String capitalize(final String s) {
        if (s != null && s.length() > 0) {
            if (s.length() > 1) {
                return ("" + s.charAt(0)).toUpperCase() + s.substring(1);
            } else {
                return s.toUpperCase();
            }
        }
        return "";
    }

    /**
     * String.toUpperCase wrapper.
     *
     * @param s
     *            String to set to upper case.
     * @return Upper case representation of s.
     */
    public static String toUpperCase(final String s) {
        return s.toUpperCase();
    }

    /**
     * Get the keys of a text representation of a properties file. Line values
     * are expected to be separated by an '=' sign.
     *
     * @param s
     *            String to parse in to lines.
     * @return Vector<String> of keys
     */
    public static Vector<String> stringLineKeys(final String s) {
        return stringLineKeys(s, "=");
    }

    /**
     * Get the keys of a text representation of a properties file. Rarely used,
     * typically the separator is '=' and the stringLineKeys(String) method
     * would be used.
     *
     * @param s
     *            String to parse in to lines.
     * @param separator
     *            Separator String.
     * @return Vector<String> of keys
     */
    public static Vector<String> stringLineKeys(final String s,
            final String separator) {
        Vector<String> v = new Vector<String>();
        if (isPopulated(s)) {
            StringReader sr = new StringReader(s);
            BufferedReader br = new BufferedReader(sr);
            try {
                String rs = br.readLine();
                while (rs != null) {
                    v.add(getKeyFromStringRep(rs, separator));
                    rs = br.readLine();
                }
            } catch (IOException ex) {
                log(ex);
            }
        }
        return v;
    }

    /**
     * @param s
     *            String to count lines in.
     * @return Count of lines in s.
     */
    public static int lineCount(final String s) {
        if (isPopulated(s)) {
            Vector<String> v = stringLines(s);
            if (v != null) {
                return v.size();
            }
        }
        return -1;
    }

    /**
     * @param s
     *            String to count lines.
     * @return Count of lines in s, not including blank lines.
     */
    public static int lineCountSkipBlanks(final String s) {
        if (isPopulated(s)) {
            Vector<String> v = stringLinesSkipBlanks(s);
            if (v != null) {
                return v.size();
            }
        }
        return -1;
    }

    /**
     * Convert a String to a Vector<String> separated by return characters.
     *
     * @param s
     *            String to parse in to lines.
     * @return Vector<String> of lines of text.
     */
    public static Vector<String> stringLines(final String s) {
        Vector<String> v = new Vector<String>();
        if (isPopulated(s)) {
            StringReader sr = new StringReader(s);
            BufferedReader br = new BufferedReader(sr);
            try {
                String rs = br.readLine();
                while (rs != null) {
                    v.add(rs);
                    rs = br.readLine();
                }
            } catch (IOException ex) {
                log(ex);
            }
        }
        return v;
    }

    /**
     * Convert a String to a Vector<String> separated by return characters.
     * Ignore empty lines.
     *
     * @param s
     *            String to parse in to lines.
     * @return Vector<String> of lines of text.
     */
    public static Vector<String> stringLinesSkipBlanks(final String s) {
        Vector<String> v = new Vector<String>();
        if (isPopulated(s)) {
            StringReader sr = new StringReader(s);
            BufferedReader br = new BufferedReader(sr);
            try {
                String rs = br.readLine();
                while (rs != null) {
                    if (isPopulated(rs.trim())) {
                        v.add(rs);
                    }
                    rs = br.readLine();
                }
            } catch (IOException ex) {
                log(ex);
            }
        }
        return v;
    }

    /**
     * Load Properties from a file.
     *
     * @param f
     *            File to load property values from.
     * @return new Properties object.
     */
    public static Map<String, String> loadPropsFromFile(final File f) {
        Properties p = new Properties();
        loadProperties(p, f);
        return loadMapFromProperties(p);
    }

    /**
     * Load Map<String, String> from Properties.
     *
     * @param p
     *            Properties to load Map<String, String> from.
     * @return Map<String, String>
     */
    public static Map<String, String> loadMapFromProperties(
            final Properties p) {
        final Map<String, String> rtn = new HashMap<String, String>();
        for (Object key : p.keySet()) {
            rtn.put(key.toString(), p.getProperty(key.toString()));
        }
        return rtn;
    }


    /**
     * Wrapper for java.io.File.getCanonicalPath method, to handle IOException.
     * If an IOException is caught, the exception is logged by the log function,
     * and the results of the Exception.getMessage function are returned.
     *
     * @param f
     *            File to get canonical path of.
     * @return result of java.io.File.getCanonicalPath
     */
    public static String getCanonicalPath(final File f) {
        try {
            if (f == null) {
                return "";
            } else {
                return f.getCanonicalPath();
            }
        } catch (IOException e) {
            log(e);
            return e.getMessage();
        }
    }

    /**
     * Get the parent directory, fully qualified even if the file is based on a
     * relative path.
     *
     * @param f
     *            File.
     * @return Parent directory.
     */
    public static String getParentDirectory(final File f) {
        if (f.exists()) {
            File fullFile = new File(getCanonicalPath(f));
            return getCanonicalPath(fullFile.getParentFile());
        }
        return getCanonicalPath(f.getParentFile());
    }

    /**
     * Load properties from a file.
     *
     * @param p
     *            Properties to load values in to.
     * @param f
     *            File to load properties from.
     */
    public static void loadProperties(final Properties p, final File f) {
        if (!f.exists()) {
            log(x("Skipping properties load, file") + " "
                    + getCanonicalPath(f));
            return;
        }
        try {
            FileReader fr = new FileReader(f);
            BufferedReader br = new BufferedReader(fr);
            p.load(br);
            br.close();
            fr.close();
        } catch (IOException e) {
            log(e);
        }
    }

    /**
     * Save properties to a file.
     *
     * @param p
     *            Properties to save to file.
     * @param f
     *            File to store properties.
     */
    public static void saveProperties(final Properties p, final File f) {
        try {
            FileOutputStream fos = new FileOutputStream(f);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            p.store(bos, new Date().toString());
            bos.flush();
            bos.close();
            fos.close();
        } catch (ClassCastException e) {
            dump(p);
            log(e);
        } catch (IOException e) {
            log(e);
        }
    }

    /**
     * Sort a set.
     *
     * @param set
     *            Set to sort.
     * @return Sorted set.
     */
    public static SortedSet<Object> sort(final Set<Object> set) {
        SortedSet<Object> ss = Collections
                .synchronizedSortedSet(new TreeSet<Object>());
        for (Object o : set) {
            ss.add(o);
        }
        return ss;
    }

    /**
     * This is a good example of how the <b>e</b> exec command is useful.
     * Anywhere that &lt;%e getCurrentDateTime%&gt; appears, the output will be
     * replaced with a nice time stamp. Useful for automatically putting a
     * "last modified" time on your HTML page.
     *
     * @see com.panopset.flywheel.CommandExecute
     * @see com.panopset.Commons#CLOCK_FORMAT
     * @return String the nicely formatted time stamp.
     */
    public static String getCurrentDateTime() {
        return CLOCK_FORMAT.format(new Date());
    }

    /**
     * This is a another good example of how the <b>e</b> exec command is
     * useful. Use this to set the last-modified header meta tag.
     *
     * @see com.panopset.flywheel.CommandExecute
     * @see com.panopset.Commons#LAST_MODIFIED_FORMAT
     * @return String current date formatted for html meta tag last-modified.
     */
    public static String getLastModifiedDate() {
        return LAST_MODIFIED_FORMAT.format(new Date());
    }

    /**
     * Get current date.
     *
     * @see com.panopset.flywheel.CommandExecute
     * @see com.panopset.Commons#TIMESTAMP_FORMAT
     * @return Current date in timestamp format.
     */
    public static String getCurrentDate() {
        return TIMESTAMP_FORMAT.format(new Date());
    }

    /**
     * Check for match, useful in menu generation.
     *
     * If s1 is equal to s2, return r1, otherwise return r2.
     *
     * @param s1
     *            String 1.
     * @param s2
     *            String 2.
     * @param r1
     *            Result 1, to return if s1 = s2.
     * @param r2
     *            Result 2, to return if s1 <> s2.
     * @return String r1 if s1 = s2, r2 if s1 <> s2.
     */
    public static String check4match(final String s1, final String s2,
            final String r1, final String r2) {
        if (s1 == null || s2 == null || r1 == null || r2 == null) {
            return "";
        }
        if (s1.equals(s2)) {
            return r1;
        }
        return r2;
    }

    /**
     * Return true if the passed String is not null or empty.
     *
     * @param s
     *            String to test.
     * @return false if null or empty String, true otherwise
     */
    public static boolean isPopulated(final String s) {
        return s != null && !"".equals(s);
    }

    /**
     * Fill String to a given length with a given String.
     *
     * @param str
     *            String to fill.
     * @param length
     *            Length to fill to.
     * @param filler
     *            String to fill left.
     * @return Adjusted String.
     */
    public static String fillStringLeft(final String str, final int length,
            final String filler) {
        StringBuffer sb = new StringBuffer();
        int m = str.length();
        while (m++ < length) {
            sb.append(filler);
        }
        sb.append(str);
        return sb.toString();
    }

    /**
     * Pad a String with a given character. If str param is longer than width,
     * str will be returned un-changed.
     *
     * @param str
     *            String to be padded
     * @param fill
     *            char to pad the String with
     * @param width
     *            How wide the result String is to be
     * @param left
     *            Pad left if true, right if false
     * @return String padded left or right with fill char
     */
    public static String pad(final String str, final char fill,
            final int width, final boolean left) {
        StringBuffer sb = new StringBuffer();
        String s = "";
        if (str != null) {
            s = str;
        }
        int l = s.length();
        if (l > (width - 1)) {
            return s;
        }
        synchronized (sb) {
            if (!left) {
                sb.append(s);
            }
            for (int i = l; i < width; i++) {
                sb.append(fill);
            }
            if (left) {
                sb.append(s);
            }
        }
        return sb.toString();
    }

    /**
     * centerInScreen returns a point that can be used in a window setLocation
     * call to place the window in the center of the screen.
     *
     * @param d
     *            For example, the Dimension of the window from a getSize()
     *            call.
     * @return Point Returns upper left corner point that would place the window
     *         in the center.
     */
    public static Point centerInScreen(final Dimension d) {
        final Dimension s = Toolkit.getDefaultToolkit().getScreenSize();
        return new Point(((s.width - d.width) / 2),
                ((s.height - d.height) / 2));
    }

    /**
     * Center a Dimension in a Rectangle.
     *
     * @param d
     *            Dimension to center
     * @param r
     *            Rectangle to center Dimension in
     * @return center of r
     */
    public static Point centerInRect(final Dimension d, final Rectangle r) {
        return new Point(r.x + ((int) (r.width - d.width) / 2), r.y
                + ((int) (r.height - d.height) / 2));
    }

    /**
     * Copy a JVM path resource to a file.
     *
     * @param resourcePath
     *            ie com/company/favicon.png
     * @param targetPath
     *            ie html/images/icon16x16.png. If called from Flywheel, the
     *            targetPath is relative to the target directory.
     * @throws IOException
     *             IO Exception.
     */
    public static void copyLibraryResource(final String resourcePath,
            final String targetPath) throws IOException {
        copyLibraryResource(resourcePath, new File(targetPath));
    }

    /**
     * Copy a JVM path resource to a file.
     *
     * @param resourcePath
     *            Resource path.
     * @param targetFile
     *            Target file.
     * @throws IOException
     *             IO Exception.
     */
    public static void copyLibraryResource(final String resourcePath,
            final File targetFile) throws IOException {
        UtilIO.copyLibraryResource(resourcePath, targetFile);
    }

    /**
     * Get the URL for an application package resource.
     *
     * @param clazz
     *            Class that resides in the package the the resource is from.
     * @param resourceName
     *            Simple name of the resource.
     * @return URL for resource.
     */
    public static URL getPackageURL(final Class<?> clazz,
            final String resourceName) {
        return clazz.getResource(convertPackageToURLsyntax(clazz.getPackage()
                .getName())
                + "/" + resourceName);
    }

    /**
     * Get the text of a file in the same package of a given file.
     *
     * @param clazz
     *            Class in same package where file resides.
     * @param name
     *            file name.
     * @return Text contents of file.
     */
    public static String getPackageText(final Class<?> clazz,
            final String name) {
        URL url = getPackageURL(clazz, name);
        if (url == null) {
            return x("Not found: ") + clazz.getCanonicalName() + name;
        }
        return getTextFromURL(url);
    }

    /**
     * Get the text from a file represented by a URL.
     *
     * @param url
     *            Location of text file.
     * @return Text contents of file.
     */
    public static String getTextFromURL(final URL url) {
        StringBuffer sb = new StringBuffer();
        InputStreamReader isr = null;
        BufferedReader br = null;
        String s = null;
        try {
            isr = new InputStreamReader(url.openStream());
            br = new BufferedReader(isr);
            while ((s = br.readLine()) != null) {
                sb.append(s).append(Strings.getEol());
            }
        } catch (Exception e) {
            log(e);
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e1) {
                log(e1);
            }
            try {
                if (isr != null) {
                    isr.close();
                }
            } catch (IOException e2) {
                log(e2);
            }
        }
        return sb.toString();
    }

    /** Match period. */
    public static final Pattern REGEX_PATTERN_JAVA_PACKAGE = Pattern
            .compile("\\.");

    /**
     * Convert package to URL syntax.
     *
     * @param s
     *            Dot separated package name.
     * @return Forward slash separated path.
     */
    public static String convertPackageToURLsyntax(final String s) {
        return "/" + REGEX_PATTERN_JAVA_PACKAGE.matcher(s).replaceAll("/");
    }

    /**
     * Get an Integer from a property file, using the given key.
     *
     * @param p
     *            Properties file.
     * @param key
     *            Key.
     * @return null if property not found or not convertable to an int,
     *         otherwise the int value.
     */
    public static Integer getIntegerFromProperty(final Properties p,
            final String key) {
        if (p == null) {
            return null;
        }
        if (key == null) {
            return null;
        }
        Object o = p.get(key);
        if (o == null) {
            return null;
        }
        String s = o.toString();
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            log(e);
            return null;
        }
    }

    /**
     * Generate a unique key independent of run time, based on stack trace.
     *
     * @return String "key_" + md5sum(getStackTrace)
     */
    public static String generateStackHashKey() {
        Exception e = null;
        try {
            throw new Exception();
        } catch (Exception ex) {
            e = ex;
        }
        MessageDigest digest;
        try {
            digest = java.security.MessageDigest.getInstance("MD5");
            digest.update(getStackTrace(e).getBytes());
            byte[] hash = digest.digest();
            return Base64.encode(new String(hash));
        } catch (NoSuchAlgorithmException e1) {
            log(e1);
        }
        return "";
    }

    /**
     * @param t
     *            Throwable to get the stack trace from.
     * @return Stack trace.
     */
    public static String getStackTrace(final Throwable t) {
        StringWriter sw = new StringWriter();
        try {
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            pw.flush();
            pw.close();
            sw.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return sw.toString();
    }

    /**
     * @param t
     *            Throwable to get the stack trace from.
     * @return Full stack trace, including causes.
     */
    public static String getFullStackTrace(final Throwable t) {
        StringWriter sw = new StringWriter();
        sw.append("*************************");
        sw.append(Strings.getEol());
        sw.append(getStackTrace(t));
        sw.append(Strings.getEol());
        Throwable cause = t.getCause();
        while (cause != null) {
            sw.append("*************************");
            sw.append(Strings.getEol());
            sw.append(getStackTrace(cause));
            sw.append(Strings.getEol());
            cause = cause.getCause();
        }
        return sw.toString();
    }

    /**
     * Get the String page source of an http page.
     *
     * @param urlStr
     *            URL String.
     * @return page source String.
     * @throws Exception
     *             Exception.
     */
    public static String httpGet(final String urlStr) throws Exception {
        StringBuffer sb = new StringBuffer();
        try {
            URL site = new URL(urlStr);
            HttpURLConnection conn = (HttpURLConnection) site.openConnection();
            conn.setReadTimeout(getConnectTimeout());
            InputStream in = conn.getInputStream();
            byte[] buff = new byte[Numbers.Integers.BUFFER_SIZE.getValue()];
            int count = 0;
            synchronized (sb) {
                while ((count = in.read(buff)) > 0) {
                    sb.append(new String(buff, 0, count));
                }
            }
            in.close();
        } catch (Exception e) {
            log(e);
            return e.getMessage();
        }
        return sb.toString();
    }

    /**
     * Default connect timeout is 300000 (5 minutes).
     */
    private static final Integer CONNECT_TIMEOUT_DEFAULT = 300000;

    /**
     * Connect timeout.
     */
    private static Integer connectTimeout = CONNECT_TIMEOUT_DEFAULT;

    /**
     * Get connection timeout. Default is 25000 (25 seconds).
     *
     * @return timeout, in milliseconds.
     */
    public static Integer getConnectTimeout() {
        return connectTimeout;
    }

    /**
     * Set the connection timeout.
     *
     * @param value
     *            in milliseconds.
     */
    public static void setConnectTimeout(final int value) {
        connectTimeout = value;
        connectTimeoutString = null;
    }

    /**
     * Connection timeout.
     */
    private static String connectTimeoutString;

    /**
     * getConnectionTimeout as a String.
     *
     * @return String representation of getConnectTimeout.
     */
    public static String getConnectTimeoutString() {
        if (connectTimeoutString == null) {
            connectTimeoutString = "" + getConnectTimeout();
        }
        return connectTimeoutString;
    }

    /**
     * If you know how much memory is required for an operation, pass that
     * amount in bytes to this method.
     *
     * @param needed
     *            How much memory is needed.
     * @return true iff enough memory is available.
     */
    public static boolean checkMemory(final long needed) {
        long fm = getFreeMemory();
        if (needed > fm) {
            int length = maxWidth(fm, needed);
            log(x("Available memory: ") + fillStringLeft("" + fm, length, "0"));
            log(x("Memory needed   : ")
                    + fillStringLeft("" + needed, length, "0"));
            dspmsg(x("Not enough memory, try increasing memory, for example:"));
            dspmsg("-Xms256m -Xmx1024m");
            return false;
        }
        return true;
    }

    /**
     * Returns the maximum length of two objects, after conversion to String.
     * Primitives may be passed in due to the Java 5 autoboxing feature. If both
     * are null, -1 is returned.
     *
     * @param o0
     *            Object 0
     * @param o1
     *            Object 1
     * @return Width of o0 or o1, depending on which is longer.
     */
    public static int maxWidth(final Object o0, final Object o1) {
        if (o0 == null && o1 == null) {
            return -1;
        }
        if (o0 == null) {
            return ("" + o1).length();
        }
        if (o1 == null) {
            return ("" + o0).length();
        }
        return Math.max(("" + o0).length(), ("" + o1).length());
    }

    /**
     * @return Free memory, in bytes.
     */
    public static long getFreeMemory() {
        return Runtime.getRuntime().freeMemory();
    }

    /**
     * @return Total memory, in bytes.
     */
    public static long getTotalMemory() {
        return Runtime.getRuntime().totalMemory();
    }

    /**
     * @return Maximum memory, in bytes.
     */
    public static long getMaxMemory() {
        return Runtime.getRuntime().maxMemory();
    }

    /**
     * Given a Vector of Strings, return a Vector of Strings with any extensions
     * dropped off. ie: x.txt becomes x.
     *
     * @param v
     *            Vector.
     * @return Vector&lt;String&gt;
     */
    public static Vector<String> dropExtensions(final Vector<String> v) {
        Vector<String> rtn = new Vector<String>();
        for (String s : v) {
            rtn.add(dropExtension(s));
        }
        return rtn;
    }

    /**
     * Drop path from String, /foo/bar becomes bar.
     *
     * @param s
     *            If empty or null, an empty String will be returned.
     * @return String with paths dropped off.
     */
    public static String dropPath(final String s) {
        if (!isPopulated(s)) {
            return "";
        }
        int i = s.lastIndexOf("/");
        if (i > -1 && s.length() > i) {
            return s.substring(i + 1);
        } else {
            return s;
        }
    }

    /**
     * Drop extension from String, x.txt becomes x.
     *
     * @param s
     *            If empty or null, an empty String will be returned.
     * @return String with extension dropped off.
     */
    public static String dropExtension(final String s) {
        if (!isPopulated(s)) {
            return "";
        }
        int i = s.lastIndexOf(".");
        if (i > -1) {
            return s.substring(0, i);
        } else {
            return s;
        }
    }

    /**
     * Get file extension, ie <b>.txt</b>.
     *
     * @param f
     *            File.
     * @return Extension, if any, or blank.
     */
    public static String getExtension(final File f) {
        return getExtension(getCanonicalPath(f));
    }

    /**
     * Get extension from String, ie <b>.txt</b>.
     *
     * @param s
     *            String to check.
     * @return Extension, if any, or blank.
     */
    public static String getExtension(final String s) {
        if (isPopulated(s)) {
            int i = s.lastIndexOf(".");
            if (i > -1) {
                return s.substring(i);
            }
        }
        return "";
    }

    /**
     * Get property value from a String representation of a property as stored
     * as a Property file line.
     *
     * @param src
     *            String representation of a property as stored, ie: a=b
     * @return Everything after the = sign, or an empty String object if src is
     *         null or not in a property format.
     */
    public static String getPropFromStringRep(final String src) {
        if (isPopulated(src)) {
            int i = src.indexOf("=");
            if (i > -1) {
                String spliced = src.substring(i);
                if (spliced.length() > 1) {
                    return spliced.substring(1);
                }
            }
        }
        return "";
    }

    /**
     *
     * Get key from String representation of name value pair.
     *
     * @param src
     *            Source.
     * @param sep
     *            Separator.
     * @return The key.
     */
    public static String getKeyFromStringRep(final String src,
            final String sep) {
        if (isPopulated(src)) {
            int i = src.indexOf(sep);
            if (i > -1) {
                return (src.substring(0, i));
            }
        }
        return "";
    }

    /**
     * Prevent instantiation.
     */
    private Util() {
    }
}
